package http

import (
	"context"
	"crypto/tls"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/go-resty/resty/v2"

	"testkjcld.bhecard.com/gitlab/kit/esim/log"
	"testkjcld.bhecard.com/gitlab/kit/esim/proxy"
)

// GlobalStub is test double and it is used when we cannot or don’t want to involve real server.
// Instead of the real server, we introduced a stub and defined what data should be returned.
// Example:
//
//	func(request *http.Request) *http.Response {
//		resp := &http.Response{}
//		if request.URL.String() == "127.0.0.1" {
//			resp.StatusCode = http.StatusOK
//			resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
//		}
//		return resp
//	}
var GlobalStub func(*http.Request) *http.Response

type Client struct {
	logger log.Logger

	transport *http.Transport

	transports []func() interface{}

	Client *resty.Client // go-resty用法灵活，大写公开，不必局限于该文件下的SendPOST、SendGET等方法；
}

type Options func(*Client)

type ClientOptions struct{}

func NewClient(opts ...Options) *Client {
	c := &Client{
		Client:    resty.New(),
		transport: http.DefaultTransport.(*http.Transport),
	}

	for _, opt := range opts {
		opt(c)
	}

	if c.logger == nil {
		c.logger = log.NewLogger()
	}

	if GlobalStub != nil {
		// It is the last transports.
		c.transports = append(c.transports, newGlobalStub(GlobalStub,
			c.logger))
	}

	if c.transports == nil {
		c.Client.SetTransport(c.transport)
	} else {
		c.Client.SetTransport(proxy.NewProxyFactory().
			GetFirstInstance("http", c.transport,
				c.transports...).(http.RoundTripper))
	}

	return c
}

// 忽略证书校验
func (ClientOptions) WithInsecureSkip() Options {
	return func(hc *Client) {
		hc.transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
	}
}

// 配置代理,增加校验功能，防止出现
func (ClientOptions) WithHttpProxy(proxyUrl string) Options {
	return func(hc *Client) {
		if _, err := url.Parse(proxyUrl); proxyUrl == "" || err != nil {
			return
		}
		hc.transport.Proxy = func(req *http.Request) (*url.URL, error) {
			return url.Parse(proxyUrl)
		}
	}
}

// http 长连接 WithDisableKeepAlive
func (ClientOptions) WithDisableKeepAlive(b bool) Options {
	return func(hc *Client) {
		hc.transport.DisableKeepAlives = b
	}
}

// 超时
func (ClientOptions) WithTimeOut(timeout time.Duration) Options {
	return func(hc *Client) {
		hc.Client.SetTimeout(timeout * time.Second)
	}
}

func (ClientOptions) WithLogger(logger log.Logger) Options {
	return func(hc *Client) {
		hc.logger = logger
	}
}

func (ClientOptions) WithProxy(proxys ...func() interface{}) Options {
	return func(hc *Client) {
		hc.transports = append(hc.transports, proxys...)
	}
}

func (c *Client) SendGet(ctx context.Context, addr string) (rtyResp *resty.Response, err error) {
	return c.Client.R().SetContext(ctx).EnableTrace().Get(strings.TrimSpace(addr))
}

func (c *Client) SendPost(ctx context.Context, addr, contentType string, body io.Reader) (rtyResp *resty.Response, err error) {
	return c.Client.R().SetContext(ctx).EnableTrace().
		SetHeader("Content-Type", contentType).
		SetBody(body).
		Post(strings.TrimSpace(addr))
}

func (c *Client) CloseIdleConnections(ctx context.Context) {
	c.Client.SetCloseConnection(true)
}

func newGlobalStub(stubFunc func(*http.Request) *http.Response,
	logger log.Logger) func() interface{} {
	return func() interface{} {
		stubsProxyOptions := StubsProxyOptions{}
		stubsProxy := NewStubsProxy(
			stubsProxyOptions.WithRespFunc(stubFunc),
			stubsProxyOptions.WithName("global stub"),
			stubsProxyOptions.WithLogger(logger),
		)

		return stubsProxy
	}
}
